package derk.sam;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import derk.utils.StringTool;

public class SerialRXTX {
    public SerialPort mSerialPort = null;
    public String serialPortNumberString = null;

    final static int DEVICE_NO_RESPONSE_TIME = 5000;  //设备无响应等待时间5000ms
    private onReceiveDataListener mOnReceiveDataListener;
    public onReceiveDataListener gOnReceiveDataListener;

    public interface onReceiveDataListener {
        public void OnReceiverData(String portNumberString, byte[] data);
    }

    public void setOnReceiveDataListener(onReceiveDataListener l) {
        this.gOnReceiveDataListener = l;
    }

    /**
     * This code snippet shows how to retrieve the available comms ports on your
     * computer. A CommPort is available if it is not being used by another
     * application.
     */
    public void listAvailablePorts() {
        HashSet<CommPortIdentifier> portSet = getAvailableSerialPorts();
        String[] serialPort = new String[portSet.size()];
        int i = 0;
        for (CommPortIdentifier comm : portSet) {
            serialPort[i] = comm.getName();
            System.out.println(serialPort[i]);
            i++;
        }
    }

    public String getName() {
        return serialPortNumberString;
    }

    public String getPortTypeName(int portType) {
        switch (portType) {
            case CommPortIdentifier.PORT_I2C:
                return "I2C";
            case CommPortIdentifier.PORT_PARALLEL:
                return "Parallel";
            case CommPortIdentifier.PORT_RAW:
                return "Raw";
            case CommPortIdentifier.PORT_RS485:
                return "RS485";
            case CommPortIdentifier.PORT_SERIAL:
                return "Serial";
            default:
                return "unknown type";
        }
    }

    /**
     * @return A HashSet containing the CommPortIdentifier for all serial ports
     *         that are not currently being used.
     */
    public HashSet<CommPortIdentifier> getAvailableSerialPorts() {
        HashSet<CommPortIdentifier> h = new HashSet<CommPortIdentifier>();
        @SuppressWarnings("rawtypes")
        Enumeration thePorts = CommPortIdentifier.getPortIdentifiers();// 可以找到系统的所有的串口，每个串口对应一个CommPortldentifier
        while (thePorts.hasMoreElements()) {
            CommPortIdentifier com = (CommPortIdentifier) thePorts
                    .nextElement();
            switch (com.getPortType()) {
                case CommPortIdentifier.PORT_SERIAL:// type of the port is serial
                    try {
                        CommPort thePort = com.open("CommUtil", 50);// open the serialPort
                        thePort.close();
                        h.add(com);;
                    } catch (PortInUseException e) {
                        System.out.println("Port, " + com.getName()
                                + ", is in use.");
                    } catch (Exception e) {
                        System.err.println("Failed to open port " + com.getName());
                        e.printStackTrace();
                    }
            }
        }
        return h;
    }

//    public static synchronized SerialRXTX connect(String portName) throws Exception {
//    	SerialRXTX serialRXTX = new SerialRXTX();
//
//        CommPortIdentifier portIdentifier = CommPortIdentifier
//                .getPortIdentifier(portName);// initializes of port operation
//
//        if (portIdentifier.isCurrentlyOwned()) {
//            System.out.println("Error: Port is currently in use");
//            throw new Exception("Error: Port is currently in use");
//        } else {
//            CommPort commPort = portIdentifier.open(portName, 5000);// the delay
//                                                                    // time of
//                                                                    // opening
//                                                                    // port
//            if (commPort instanceof SerialPort) {
//            	serialRXTX.mSerialPort = (SerialPort) commPort;
//            	serialRXTX.mSerialPort.setSerialPortParams(115200, SerialPort.DATABITS_8,
//                        SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);// serial
//                                                                        // communication
//                                                                        // parameters
//                                                                        // setting
//                InputStream inputStream = serialRXTX.mSerialPort.getInputStream();
//                serialRXTX.mSerialPort.addEventListener(serialRXTX.new SerialReader(inputStream));
//                serialRXTX.mSerialPort.notifyOnDataAvailable(true);
//                serialRXTX.serialPortNumberString = portName;
//            }
//        }
//        return serialRXTX;
//    }

    public SerialPort connect(String portName) throws Exception {
        CommPortIdentifier portIdentifier = CommPortIdentifier
                .getPortIdentifier(portName);// initializes of port operation

        if (portIdentifier.isCurrentlyOwned()) {
            System.out.println("Error: Port is currently in use");
            throw new Exception("Error: Port is currently in use");
        } else {
            CommPort commPort = portIdentifier.open(portName, 5000);// the delay
            // time of
            // opening
            // port
            if (commPort instanceof SerialPort) {
                mSerialPort = (SerialPort) commPort;
                mSerialPort.setSerialPortParams(115200, SerialPort.DATABITS_8,
                        SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);// serial
                // communication
                // parameters
                // setting
                InputStream inputStream = mSerialPort.getInputStream();
                mSerialPort.addEventListener(new SerialReader(inputStream));
                mSerialPort.notifyOnDataAvailable(true);
                serialPortNumberString = portName;
            }
        }
        return mSerialPort;
    }

    public void close() {
        if (mSerialPort != null) {
            mSerialPort.notifyOnDataAvailable(false);
            mSerialPort.removeEventListener();
            mSerialPort.close();
            mSerialPort = null;
        }
    }

    /**
     * 串口发送数据
     * @param serialPort 要发数据的串口
     * @param msg 要发送的数据
     * @throws IOException
     */
    public void sendMessage(SerialPort serialPort, byte[] msg) throws IOException {
        try {
            OutputStream outputStream = serialPort.getOutputStream();
            outputStream.write(msg);
            System.out.println( "[" + new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss:SSS").format(new Date()) + "]" + serialPortNumberString + "发送数据("+ msg.length +")：" + StringTool.byteHexToSting(msg) );
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 串口发送数据
     * @param msg 要发送的数据
     * @throws IOException
     */
    public void sendMessage(byte[] msg) throws IOException {
        sendMessage(mSerialPort, msg);
    }

    /**
     * 串口发送数据, 异步回调方式
     * @param serialPort 要发数据的串口
     * @param msg 要发送的数据
     * @param listener 如果在超时时间内返回数据，则会回调到这里
     * @throws IOException
     */
    public void sendMessage(SerialPort serialPort, byte[] msg, onReceiveDataListener listener) throws IOException {
        mOnReceiveDataListener = listener;
        sendMessage(serialPort, msg);
    }

    /**
     * 串口发送数据, 异步回调方式
     * @param serialPort 要发数据的串口
     * @param msg 要发送的数据
     * @param listener 如果在超时时间内返回数据，则会回调到这里
     * @throws IOException
     */
    public void sendMessage(byte[] msg, onReceiveDataListener listener) throws IOException {
        mOnReceiveDataListener = listener;
        sendMessage(msg);
    }

    /**
     * 串口发送数据, 同步阻塞方式
     * @param serialPort 要发数据的串口
     * @param msg 要发送的数据
     * @throws DeviceNoResponseException
     */
    public byte[] sendMessageWithReturn(SerialPort serialPort, byte[] msg) throws DeviceNoResponseException {
        synchronized(this) {
            if (msg == null || msg.length == 0) {
                throw new DeviceNoResponseException("数据不能为null");
            }

            if (serialPort == null) {
                throw new DeviceNoResponseException("串口未打开！");
            }

            final byte[][] returnBytes = new byte[1][1];

            final Semaphore semaphore = new Semaphore(0);
            returnBytes[0] = null;

            try {
                sendMessage(serialPort, msg, new onReceiveDataListener() {
                    @Override
                    public void OnReceiverData(String portNumberString, byte[] data) {
                        returnBytes[0] = data;
                        semaphore.release();
                    }
                });
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                throw new DeviceNoResponseException("发送数据失败！");
            }

            try {
                boolean getAccquire = semaphore.tryAcquire(DEVICE_NO_RESPONSE_TIME, TimeUnit.MILLISECONDS);
                if (!getAccquire) {
                    throw new DeviceNoResponseException("设备返回数据超时！");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new DeviceNoResponseException("设备返回数据超时！");
            }

            return returnBytes[0];
        }
    }

    /**
     * 串口发送数据, 同步阻塞方式
     * @param msg 要发送的数据
     * @return 串口返回的数据
     * @throws DeviceNoResponseException
     */
    public byte[] sendMessageWithReturn(byte[] msg) throws DeviceNoResponseException {
        synchronized(this) {
            if (msg == null || msg.length == 0) {
                throw new DeviceNoResponseException("数据不能为null");
            }

            if (mSerialPort == null) {
                throw new DeviceNoResponseException("串口未打开");
            }

            final byte[][] returnBytes = new byte[1][1];

            final Semaphore semaphore = new Semaphore(0);
            returnBytes[0] = null;

            try {
                sendMessage(mSerialPort, msg, new onReceiveDataListener() {
                    @Override
                    public void OnReceiverData(String portNumberString, byte[] bytApduRtnData) {
                        returnBytes[0] = bytApduRtnData;
                        semaphore.release();
                    }
                });
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                throw new DeviceNoResponseException("发送数据失败！");
            }

            try {
                boolean getAccquire = semaphore.tryAcquire(DEVICE_NO_RESPONSE_TIME, TimeUnit.MILLISECONDS);
                if (!getAccquire) {
                    throw new DeviceNoResponseException("设备返回数据超时！");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new DeviceNoResponseException("设备返回数据超时！");
            }

            return returnBytes[0];
        }
    }

    /**
     * 串口发送数据, 同步阻塞方式
     * @param msg 要发送的数据
     * @param timeout 超时时间
     * @return 串口返回的数据
     * @throws DeviceNoResponseException
     */
    public byte[] sendMessageWithReturn(byte[] msg, int timeout) throws DeviceNoResponseException {
        synchronized(this) {
            if (msg == null || msg.length == 0) {
                throw new DeviceNoResponseException("数据不能为null");
            }

            if (mSerialPort == null) {
                throw new DeviceNoResponseException("串口未打开");
            }

            final byte[][] returnBytes = new byte[1][1];

            final Semaphore semaphore = new Semaphore(0);
            returnBytes[0] = null;

            try {
                sendMessage(mSerialPort, msg, new onReceiveDataListener() {
                    @Override
                    public void OnReceiverData(String portNumberString, byte[] bytApduRtnData) {
                        returnBytes[0] = bytApduRtnData;
                        semaphore.release();
                    }
                });
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                throw new DeviceNoResponseException("发送数据失败！");
            }

            try {
                boolean getAccquire = semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
                if (!getAccquire) {
                    throw new DeviceNoResponseException("设备返回数据超时！");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new DeviceNoResponseException("设备返回数据超时！");
            }

            return returnBytes[0];
        }
    }

    /**
     * 串口发送数据, 同步阻塞方式
     * @param timeout 接收数据超时时间，单位ms
     * @return 串口返回的数据
     * @throws DeviceNoResponseException
     */
    public byte[] getReturnBytes(int timeout) throws DeviceNoResponseException{
        synchronized(this) {
            if (mSerialPort == null) {
                throw new DeviceNoResponseException("串口未打开");
            }

            final byte[][] returnBytes = new byte[1][1];

            final Semaphore semaphore = new Semaphore(0);
            returnBytes[0] = null;

            mOnReceiveDataListener = new onReceiveDataListener() {
                @Override
                public void OnReceiverData(String portNumberString, byte[] data) {
                    returnBytes[0] = data;
                    semaphore.release();
                }
            };

            try {
                boolean getAccquire = semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
                if (!getAccquire) {
                    throw new DeviceNoResponseException("设备返回数据超时！");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new DeviceNoResponseException("设备返回数据超时！");
            }

            return returnBytes[0];
        }
    }

    /**
     * Handles the input coming from the serial port. A new line character is
     * treated as the end of a block in this example.
     */
    public class SerialReader implements SerialPortEventListener {
        private InputStream in;

        public SerialReader(InputStream in) {
            this.in = in;
        }

        public void serialEvent(SerialPortEvent event) {
            switch (event.getEventType()) {
                case SerialPortEvent.BI: // 10 通讯中断
                    System.out.println("<SerialRxtx.java>串口通信中断！");
                    break;
                case SerialPortEvent.OE: // 7 溢位（溢出）错误
                    break;
                case SerialPortEvent.FE: // 9 帧错误
                    break;
                case SerialPortEvent.PE: // 8 奇偶校验错误
                    break;
                case SerialPortEvent.CD: // 6 载波检测
                    break;
                case SerialPortEvent.CTS: // 3 清除待发送数据
                    break;
                case SerialPortEvent.DSR: // 4 待发送数据准备好了
                    break;
                case SerialPortEvent.RI: // 5 振铃指示
                    break;
                case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2 输出缓冲区已清空
                    break;
                case SerialPortEvent.DATA_AVAILABLE: // 1 串口存在可用数据
                    byte[] buffer = new byte[50000];
                    byte[] temp = new byte[50000];
                    if(event.getEventType() == SerialPortEvent.DATA_AVAILABLE){
                        try {
                            int readLen = 0;
                            // receive the data
                            while ( (in != null) && (in.available() > 0) && ((in.available() + readLen) < 50000) ) {
                                int len = in.read(temp);
                                System.arraycopy(temp, 0, buffer, readLen, len);
                                readLen += len;

                                int cnt = 0;
							    if (len > 500) {
								    while ( (in.available() <= 0) && (cnt++ < 15) ) {
								        try {
											Thread.sleep(10);
										} catch (InterruptedException e) {
											// TODO Auto-generated catch block
											e.printStackTrace();
										}
								    }
							    }
							    else {
								    while ( (in.available() <= 0) && (cnt++ < 10) ) {
								        try {
											Thread.sleep(1);
										} catch (InterruptedException e) {
											// TODO Auto-generated catch block
											e.printStackTrace();
										}
								    }
							    }
                            }
                            byte[] readBytes = new byte[readLen];
                            System.arraycopy(buffer, 0, readBytes, 0, readLen);

                            if (mOnReceiveDataListener != null) {
                                mOnReceiveDataListener.OnReceiverData( serialPortNumberString, readBytes );
                            }

                            if (gOnReceiveDataListener != null) {
                                gOnReceiveDataListener.OnReceiverData( serialPortNumberString, readBytes );
                            }

                            System.out.println( "[" + new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss:SSS").format(new Date()) + "]" + serialPortNumberString + "接收数据("+ readBytes.length +")：" + StringTool.byteHexToSting(readBytes) );
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                            mSerialPort.close();
                        } finally {
                            try {
                                if (in != null) {
                                    in.close();
                                }
                            } catch (IOException e1) {
                                // TODO Auto-generated catch block
                                e1.printStackTrace();
                            }
                        }
                    }
                    break;
                default:
                    break;
            }
        }

    }
}